#include "hostProxy.h"
#include <glog/logging.h>
#include "JSVMConverter.h"
#include "JSVMRuntime.h"

namespace rnjsvm {

JSVM_PropertyHandlerConfigurationStruct HostObjectProxy::propertyCfg = {
        .genericNamedPropertyGetterCallback = HostObjectProxy::Getter,
        .genericNamedPropertySetterCallback = HostObjectProxy::Setter,
        .genericNamedPropertyEnumeratorCallback = HostObjectProxy::Enumerator,
        .genericNamedPropertyDeleterCallback = nullptr,
        .genericIndexedPropertyGetterCallback = HostObjectProxy::Getter,
        .genericIndexedPropertySetterCallback = HostObjectProxy::Setter,
        .genericIndexedPropertyDeleterCallback = nullptr,
        .genericIndexedPropertyEnumeratorCallback = nullptr,
        .namedPropertyData = nullptr,
        .indexedPropertyData = nullptr};

HostObjectProxy::HostObjectProxy(JSVMRuntime &rt, JSVM_Env env, std::shared_ptr<HostObject> ho)
    : HostProxy(ProxyType::T_HOST_OBJECT), hostObject(ho), rt(rt) {}

JSVM_Value HostObjectProxy::Enumerator(JSVM_Env env, JSVM_Value thisArg, JSVM_Value namedPropertyData) {
    // TODO: check
    HostObjectProxy *hostObjectProxy;
    OH_JSVM_Unwrap(env, thisArg, reinterpret_cast<void **>(&hostObjectProxy));
    // TODO: check

    const auto &propNames = hostObjectProxy->hostObject->getPropertyNames(hostObjectProxy->rt);
    JSVM_Value nameArr;
    size_t size = propNames.size();
    OH_JSVM_CreateArrayWithLength(env, size, &nameArr);
    for (uint32_t idx = 0; idx < size; ++idx) {
        JSVM_Value name = JSVMConverter::PointerValueToJSVM<PropNameID>(env, propNames[idx]);
        OH_JSVM_SetElement(env, nameArr, idx, name);
    }

    return nameArr;
}

JSVM_Value HostObjectProxy::Getter(JSVM_Env env, JSVM_Value name, JSVM_Value thisArg, JSVM_Value namedPropertyData) {
    void *data = nullptr;
    auto status = OH_JSVM_Unwrap(env, thisArg, &data);

    if (!data || status != JSVM_OK) {
        // TODO: throw error
        JSVM_Value result = nullptr;
        OH_JSVM_GetUndefined(env, &result);
        return result;
    }

    HostObjectProxy *hostObjectProxy = static_cast<HostObjectProxy *>(data);
    Value ret = Value::undefined();
    try {
        ret = hostObjectProxy->hostObject->get(hostObjectProxy->rt, JSVMConverter::make<PropNameID>(env, name));
    } catch (const JSError &error){
        JSVM_Value errorValue = JSVMConverter::JsiToJSVM(env, error.value());
        OH_JSVM_Throw(env, errorValue);
    } catch (const std::exception &ex) {
        std::string exceptionMsg("Exception in HostObjectProxy.Getter:");
        exceptionMsg += ex.what();
        OH_JSVM_ThrowError(env, nullptr, exceptionMsg.c_str());
    } catch(...) {
        std::string exceptionMsg("Exception in HostObjectProxy.Getter: <unknown>");
        OH_JSVM_ThrowError(env, nullptr, exceptionMsg.c_str());
    }
    return JSVMConverter::JsiToJSVM(env, ret);
}

JSVM_Value HostObjectProxy::Setter(JSVM_Env env, JSVM_Value name, JSVM_Value property, JSVM_Value thisArg,
                                   JSVM_Value namedPropertyData) {
    void *data = nullptr;

    auto status = OH_JSVM_Unwrap(env, thisArg, &data);

    if (!data || status != JSVM_OK) {
        // TODO: throw error
        JSVM_Value result = nullptr;
        OH_JSVM_GetUndefined(env, &result);
        return result;
    }

    HostObjectProxy *hostObjectProxy = static_cast<HostObjectProxy *>(data);
    try {
        hostObjectProxy->hostObject->set(hostObjectProxy->rt, JSVMConverter::make<PropNameID>(env, name),
                                         JSVMConverter::JSVMToJsi(env, property));
    } catch (const JSError &error){
        JSVM_Value errorValue = JSVMConverter::JsiToJSVM(env, error.value());
        OH_JSVM_Throw(env, errorValue);
        return JSVMConverter::JsiToJSVM(env, Value::undefined());
    } catch (const std::exception &ex) {
        std::string exceptionMsg("Exception in HostObjectProxy.Setter:");
        exceptionMsg += ex.what();
        OH_JSVM_ThrowError(env, nullptr, exceptionMsg.c_str());
        return JSVMConverter::JsiToJSVM(env, Value::undefined());
    } catch(...) {
        std::string exceptionMsg("Exception in HostObjectProxy.Setter: <unknown>");
        OH_JSVM_ThrowError(env, nullptr, exceptionMsg.c_str());
        return JSVMConverter::JsiToJSVM(env, Value::undefined());
    }

    // TODO: return value
    return property;
}

HostProxy *HostProxy::GetHostProxyFromJSVM(JSVM_Env env, JSVM_Value value) {
    HostProxy *hostProxy = nullptr;
    OH_JSVM_Unwrap(env, value, reinterpret_cast<void **>(&hostProxy));
    return hostProxy;
}

JSVM_Value HostFunctionProxy::FunctionCallback(JSVM_Env env, JSVM_CallbackInfo info) {
    size_t argc = 0;
    JSVM_Value thisArg = nullptr;
    void *data = nullptr;

    OH_JSVM_GetCbInfo(env, info, &argc, nullptr, &thisArg, &data);
    assert(data && "can not acquire function");

    // Get and construct jsi arguments
    constexpr uint32_t PREALLOC_SIZE = 8;
    JSVM_Value argvStack[PREALLOC_SIZE];
    Value valueStack[PREALLOC_SIZE];
    JSVM_Value *argv = argvStack;
    Value *values = valueStack;

    if (argc > PREALLOC_SIZE) {
        argv = new JSVM_Value[argc];
        values = new Value[argc];
    }

    OH_JSVM_GetCbInfo(env, info, &argc, argv, nullptr, nullptr);

    for (uint32_t idx = 0; idx < argc; ++idx) {
        values[idx] = JSVMConverter::JSVMToJsi(env, argv[idx]);
    }

    HostFunctionProxy *proxy = static_cast<HostFunctionProxy *>(data);

    Value thisVal = JSVMConverter::JSVMToJsi(env, thisArg);

    Value ret = Value::undefined();
    try {
        ret = proxy->hostFunction(proxy->rt, thisVal, values, argc);
    } catch (const JSError &error) {
        JSVM_Value errorValue = JSVMConverter::JsiToJSVM(env, error.value());
        OH_JSVM_Throw(env, errorValue);
    } catch (const std::exception &ex) {
        std::string exceptionMsg("Exception in HostFunction: ");
        exceptionMsg += ex.what();
        OH_JSVM_ThrowError(env, nullptr, exceptionMsg.c_str());
    } catch(...) {
        std::string exceptionMsg("Exception in HostFunction: <unknown>");
        OH_JSVM_ThrowError(env, nullptr, exceptionMsg.c_str());
    }

    if (argc > PREALLOC_SIZE) {
        delete[] argv;
        delete[] values;
    }

    return JSVMConverter::JsiToJSVM(env, ret);
}
}